import {
  useState, useEffect, useReducer, useRef, useCallback
} from 'react';

/**
 * 定义简单的 dva
 * @param model
 * @returns
 */
export function useReModel<T extends object>(
  model: T | (() => T),
  inject?: () => Record<string, any>
) {
  type Model = Partial<T>;
  type Payload = ((o: T) => Model | void) | Model;

  const unmountedRef = useRef(false);

  const [state, put] = useReducer(
    (oldState: T, payload: Payload) => {
      if (unmountedRef.current) {
        return oldState;
      }
      const newState = typeof payload === 'function' ? payload(oldState) : payload;

      return newState && !isCompare(newState, oldState) ? { ...oldState, ...newState } : oldState;
    },
    typeof model === 'function' ? ({} as T) : model,
    () => {
      const _model = typeof model === 'function' ? model() : model;

      if (inject) {
        return { ..._model, ...inject() };
      }

      return _model;
    }
  );

  const get = () => new Promise<T>(res => put(res));

  useEffect(() => {
    unmountedRef.current = false;

    return () => {
      unmountedRef.current = true;
    };
  }, []);

  return { state, put, get };
}

function isCompare(source: any, target: any) {
  return Object.keys(source).every(key => shallowCompare(source[key], target[key]));
}

function shallowCompare(source: any, target: any) {
  return Object.is(source, target);
}

// 缓存 keep-alive 数据
const memoMap: Record<string, any> = {};

/**
 * 定义数据上的 keep-alive
 * @param model
 * @param key
 * @returns
 */
export function useMeModel<T extends object>(model: T | (() => T), key?: string) {
  const [data] = useState(() => (key ? memoMap[key] : model) || model);

  const { state, put, get } = useReModel<T>(data, () => {
    key && (memoMap[key] = data);

    return data;
  });

  useEffect(() => {
    if (!key) {
      return;
    }
    memoMap[key] = state;
  }, [state]);

  return { state, put, get };
}

const win = window;

/**
 * 共享线程池， 同时触发
 * @param key
 * @param model
 * @returns
 */
export const useShareModel = <T extends object>(key: string, model?: T) => {
  const { state, put, get } = useReModel<T>(() => {
    let value = (model ?? {}) as T;
    if (memoMap[key]) {
      value = memoMap[key];
    }
    memoMap[key] = value;

    return value;
  });

  const publish = useCallback<typeof put>(detail => {
    const oldState = memoMap[key];
    const newState = typeof detail === 'function' ? detail(oldState) : detail;
    if (!newState) {
      return;
    }
    memoMap[key] = { ...oldState, ...newState };
    setTimeout(() => {
      win.dispatchEvent(new Event(`change.model:${key}`));
    }, 16.7);
  }, []);

  useEffect(() => {
    const change = () => {
      const newState = memoMap[key];
      put(newState);
    };

    win.addEventListener(`change.model:${key}`, change);

    return () => {
      win.removeEventListener(`change.model:${key}`, change);
    };
  }, []);

  return { state, put: publish, get };
};

export const useUpdate = () => {
  const setCount = useState(1)[1];
  const update = useCallback(() => {
    setCount(c => c + 0.1);
  }, []);

  return update;
};
